package me.mrCookieSlime.Slimefun.Objects.SlimefunItem.abstractItems;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.MaterialData;

import me.mrCookieSlime.CSCoreLibPlugin.CSCoreLib;
import me.mrCookieSlime.CSCoreLibPlugin.Configuration.Config;
import me.mrCookieSlime.CSCoreLibPlugin.general.Inventory.ChestMenu.MenuClickHandler;
import me.mrCookieSlime.CSCoreLibPlugin.general.Inventory.ClickAction;
import me.mrCookieSlime.CSCoreLibPlugin.general.Inventory.InvUtils;
import me.mrCookieSlime.CSCoreLibPlugin.general.Inventory.Item.CustomItem;
import me.mrCookieSlime.CSCoreLibPlugin.general.World.CustomSkull;
import me.mrCookieSlime.Slimefun.SlimefunStartup;
import me.mrCookieSlime.Slimefun.Lists.RecipeType;
import me.mrCookieSlime.Slimefun.Lists.SlimefunItems;
import me.mrCookieSlime.Slimefun.Objects.Category;
import me.mrCookieSlime.Slimefun.Objects.SlimefunBlockHandler;
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.UnregisterReason;
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.machines.ReactorAccessPort;
import me.mrCookieSlime.Slimefun.Setup.SlimefunManager;
import me.mrCookieSlime.Slimefun.api.BlockStorage;
import me.mrCookieSlime.Slimefun.api.energy.ChargableBlock;
import me.mrCookieSlime.Slimefun.api.energy.EnergyTicker;
import me.mrCookieSlime.Slimefun.api.inventory.BlockMenu;
import me.mrCookieSlime.Slimefun.api.inventory.BlockMenuPreset;
import me.mrCookieSlime.Slimefun.api.item_transport.ItemTransportFlow;
import me.mrCookieSlime.Slimefun.holograms.ReactorHologram;

public abstract class AReactor extends SlimefunItem {

	public static Map<Location, MachineFuel> processing = new HashMap<Location, MachineFuel>();
	public static Map<Location, Integer> progress = new HashMap<Location, Integer>();

	private static final BlockFace[] cooling =
    	{
    		BlockFace.NORTH,
    		BlockFace.NORTH_EAST,
    		BlockFace.EAST,
    		BlockFace.SOUTH_EAST,
    		BlockFace.SOUTH,
    		BlockFace.SOUTH_WEST,
    		BlockFace.WEST,
    		BlockFace.NORTH_WEST
    	};

	private Set<MachineFuel> recipes = new HashSet<MachineFuel>();

	private static final int[] border = {0, 1, 2, 3, 5, 6, 7, 8, 12, 13, 14, 21, 23};
	private static final int[] border_1 = {9, 10, 11, 18, 20, 27, 29, 36, 38, 45, 46, 47};
	private static final int[] border_2 = {15, 16, 17, 24, 26, 33, 35, 42, 44, 51, 52, 53};
	private static final int[] border_3 = {30, 31, 32, 39, 41, 48, 49, 50};
    private static final int[] border_4 = {25, 34, 43}; //No coolant border

	public AReactor(Category category, ItemStack item, String name, RecipeType recipeType, ItemStack[] recipe) {
		super(category, item, name, recipeType, recipe);

		new BlockMenuPreset(name, getInventoryTitle()) {

			@Override
			public void init() {
				constructMenu(this);
			}

			@Override
			public void newInstance(final BlockMenu menu, final Block b) {
				try {
					if (BlockStorage.getBlockInfo(b, "reactor-mode") == null){
						BlockStorage.addBlockInfo(b, "reactor-mode", "generator");
					}
					if (!BlockStorage.hasBlockInfo(b) || BlockStorage.getBlockInfo(b, "reactor-mode").equals("generator")) {
						menu.replaceExistingItem(4, new CustomItem(CustomSkull.getItem("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTM0M2NlNThkYTU0Yzc5OTI0YTJjOTMzMWNmYzQxN2ZlOGNjYmJlYTliZTQ1YTdhYzg1ODYwYTZjNzMwIn19fQ=="), "&b模式: &e节能供电", "", "&9你的反应堆将会以节能供电模式运行", "", "&6如果你的能源网络不需要能源", "&6那么它将停止发电以节省损耗", "", "&7> 点击切换模式至 &e全力供电"));
						menu.addMenuClickHandler(4, new MenuClickHandler() {

							@Override
							public boolean onClick(Player p, int arg1, ItemStack arg2, ClickAction arg3) {
								BlockStorage.addBlockInfo(b, "reactor-mode", "production");
								newInstance(menu, b);
								return false;
							}
						});
					}
					else {
						menu.replaceExistingItem(4, new CustomItem(SlimefunItems.PLUTONIUM, "&b模式: &e全力供电", "", "&9你的反应堆将会以全力供电模式运行", "", "&6如果你的能源网络不需要能源", "&6它也将会继续运行并发电...", "", "&7> 点击切换模式至 &e节能供电"));
						menu.addMenuClickHandler(4, new MenuClickHandler() {

							@Override
							public boolean onClick(Player p, int arg1, ItemStack arg2, ClickAction arg3) {
								BlockStorage.addBlockInfo(b, "reactor-mode", "generator");
								newInstance(menu, b);
								return false;
							}
						});
					}
				} catch(Exception x) {
				}
			}

			@Override
			public boolean canOpen(Block b, Player p) {
				return p.hasPermission("slimefun.inventory.bypass") || CSCoreLib.getLib().getProtectionManager().canAccessChest(p.getUniqueId(), b, true);
			}

			@Override
			public int[] getSlotsAccessedByItemTransport(ItemTransportFlow flow) {
				return new int[0];
			}
		};

		registerBlockHandler(name, new SlimefunBlockHandler() {

			@Override
			public void onPlace(Player p, Block b, SlimefunItem item) {

			}

			@Override
			public boolean onBreak(Player p, Block b, SlimefunItem item, UnregisterReason reason) {
				BlockMenu inv = BlockStorage.getInventory(b);
				if (inv != null) {
					for (int slot: getFuelSlots()) {
						if (inv.getItemInSlot(slot) != null) b.getWorld().dropItemNaturally(b.getLocation(), inv.getItemInSlot(slot));
					}
					for (int slot: getCoolantSlots()) {
						if (inv.getItemInSlot(slot) != null) b.getWorld().dropItemNaturally(b.getLocation(), inv.getItemInSlot(slot));
					}
					for (int slot: getOutputSlots()) {
						if (inv.getItemInSlot(slot) != null) b.getWorld().dropItemNaturally(b.getLocation(), inv.getItemInSlot(slot));
					}
				}
				progress.remove(b.getLocation());
				processing.remove(b.getLocation());
				ReactorHologram.remove(b.getLocation());
				return true;
			}
		});

		this.registerDefaultRecipes();
	}

	@SuppressWarnings("deprecation")
	private void constructMenu(BlockMenuPreset preset) {
		for (int i: border) {
			preset.addItem(i, new CustomItem(new MaterialData(Material.STAINED_GLASS_PANE, (byte) 7), " "),
			new MenuClickHandler() {

				@Override
				public boolean onClick(Player arg0, int arg1, ItemStack arg2, ClickAction arg3) {
					return false;
				}

			});
		}

		for (int i: border_1) {
			preset.addItem(i, new CustomItem(new MaterialData(Material.STAINED_GLASS_PANE, (byte) 5), " "),
			new MenuClickHandler() {

				@Override
				public boolean onClick(Player arg0, int arg1, ItemStack arg2, ClickAction arg3) {
					return false;
				}

			});
		}

		for (int i: border_3) {
			preset.addItem(i, new CustomItem(new MaterialData(Material.STAINED_GLASS_PANE, (byte) 13), " "),
			 new MenuClickHandler() {

				@Override
				public boolean onClick(Player arg0, int arg1, ItemStack arg2, ClickAction arg3) {
					return false;
				}

			});
		}

		preset.addItem(22, new CustomItem(new MaterialData(Material.STAINED_GLASS_PANE, (byte) 15), " "),
		new MenuClickHandler() {

			@Override
			public boolean onClick(Player arg0, int arg1, ItemStack arg2, ClickAction arg3) {
				return false;
			}

		});

		preset.addItem(1, new CustomItem(SlimefunItems.URANIUM, "&6燃料槽", "", "&7这个槽可以放入放射性燃料例如:", "&2铀 &7或 &2镎"),
		new MenuClickHandler() {

			@Override
			public boolean onClick(Player arg0, int arg1, ItemStack arg2, ClickAction arg3) {
				return false;
			}

		});

        for (int i : border_2) {
			preset.addItem(i, new CustomItem(new MaterialData(Material.STAINED_GLASS_PANE, (byte) 9), " "),
			new MenuClickHandler() {

				@Override
				public boolean onClick(Player arg0, int arg1, ItemStack arg2, ClickAction arg3) {
					return false;
				}

			});
		}

		if (needsCooling()) {
			preset.addItem(7, new CustomItem(this.getCoolant(), "&b冷却槽", "", "&e这个槽只允许放入冷却剂", "", "&c&l! &c如果没有冷却剂", "&c&l! &c你的反应堆仓将会BOOM..."));
		}
		else {
            preset.addItem(7, new CustomItem(new MaterialData(Material.BARRIER), "&b冷却槽", "", "&e这个槽只允许放入冷却剂"));

            for (int i : border_4) {
                preset.addItem(i, new CustomItem(new ItemStack(Material.BARRIER), "&c无冷却需求"),
                new MenuClickHandler() {

                    @Override
                    public boolean onClick(Player player, int i, ItemStack itemStack, ClickAction clickAction) {
                        return false;
                    }
                });
            }
        }
	}

	public abstract String getInventoryTitle();

	public abstract void registerDefaultRecipes();

	public abstract int getEnergyProduction();

	public abstract void extraTick(Location l);

	public abstract ItemStack getCoolant();

	public boolean needsCooling() {
		return getCoolant() != null;
	}

	public int[] getInputSlots() {
		return new int[] {19, 28, 37, 25, 34, 43};
	}

	public int[] getFuelSlots() {
		return new int[] {19, 28, 37};
	}

	public int[] getCoolantSlots() {
		return needsCooling() ? new int[] {25, 34, 43} : new int[]{};
	}

	public int[] getOutputSlots() {
		return new int[] {40};
	}

	public MachineFuel getProcessing(Location l) {
		return processing.get(l);
	}

	public boolean isProcessing(Location l) {
		return progress.containsKey(l);
	}

	public void registerFuel(MachineFuel fuel) {
		this.recipes.add(fuel);
	}

	@Override
	public void register(boolean slimefun) {
		addItemHandler(new EnergyTicker() {

			Set<Location> explode = new HashSet<Location>();

			@SuppressWarnings("deprecation")
			@Override
			public double generateEnergy(final Location l, SlimefunItem sf, Config data) {
				BlockMenu port = getAccessPort(l);

				if (isProcessing(l)) {
					extraTick(l);
					int timeleft = progress.get(l);
					if (timeleft > 0) {
						if (ChargableBlock.getMaxCharge(l) - ChargableBlock.getCharge(l) >= getEnergyProduction()) {
							ChargableBlock.addCharge(l, getEnergyProduction());
						}
						if (ChargableBlock.getMaxCharge(l) - ChargableBlock.getCharge(l) >= getEnergyProduction() || !BlockStorage.getBlockInfo(l, "reactor-mode").equals("generator")) {
							progress.put(l, timeleft - 1);

							Bukkit.getScheduler().scheduleSyncDelayedTask(SlimefunStartup.instance, new Runnable() {

								@Override
								public void run() {
									if (!l.getBlock().getRelative(cooling[CSCoreLib.randomizer().nextInt(cooling.length)]).isLiquid()) explode.add(l);
								}
							});


                            ItemStack item = getProgressBar().clone();
                            ItemMeta im = item.getItemMeta();
                            im.setDisplayName(" ");
                            List<String> lore = new ArrayList<String>();
                            lore.add(MachineHelper.getProgress(timeleft, processing.get(l).getTicks()));
                            lore.add(MachineHelper.getCoolant(timeleft, processing.get(l).getTicks()));
                            lore.add("");
                            lore.add(MachineHelper.getTimeLeft(timeleft / 2));
                            im.setLore(lore);
                            item.setItemMeta(im);

                            BlockStorage.getInventory(l).replaceExistingItem(22, item);

							if (needsCooling()) {
								boolean coolant = (processing.get(l).getTicks() - timeleft) % 25 == 0;

								if (coolant) {
									if (port != null) {
										for (int slot: getCoolantSlots()) {
											if (SlimefunManager.isItemSimiliar(port.getItemInSlot(slot), getCoolant(), true)) {
												port.replaceExistingItem(slot, pushItems(l, port.getItemInSlot(slot), getCoolantSlots()));
											}
										}
									}

									boolean explosion = true;
									for (int slot: getCoolantSlots()) {
										if (SlimefunManager.isItemSimiliar(BlockStorage.getInventory(l).getItemInSlot(slot), getCoolant(), true)) {
											BlockStorage.getInventory(l).replaceExistingItem(slot, InvUtils.decreaseItem(BlockStorage.getInventory(l).getItemInSlot(slot), 1));
											ReactorHologram.update(l, "&b\u2744 &7100%");
											explosion = false;
											break;
										}
									}

									if (explosion) {
										explode.add(l);
										return 0;
									}
								}
								else {
									ReactorHologram.update(l, "&b\u2744 &7" + MachineHelper.getPercentage(timeleft, processing.get(l).getTicks()) + "%");
								}
							}

							return ChargableBlock.getCharge(l);
						}
						return 0;
					}
					else {
						BlockStorage.getInventory(l).replaceExistingItem(22, new CustomItem(new MaterialData(Material.STAINED_GLASS_PANE, (byte) 15), " "));
						if (processing.get(l).getOutput() != null) pushItems(l, processing.get(l).getOutput());

						if (port != null) {
							for (int slot: getOutputSlots()) {
								// 2017-08-12 修复: 当试图放入空物品至一个反应堆访问端口时的非法参数异常
								// BlockStorage.getInventory(l).replaceExistingItem(slot, ReactorAccessPort.pushItems(port.getLocation(), BlockStorage.getInventory(l).getItemInSlot(slot)));
								if (BlockStorage.getInventory(l).getItemInSlot(slot) != null) BlockStorage.getInventory(l).replaceExistingItem(slot, ReactorAccessPort.pushItems(port.getLocation(), BlockStorage.getInventory(l).getItemInSlot(slot)));
							}
						}

						progress.remove(l);
						processing.remove(l);
						return 0;
					}
				}
				else {
					MachineFuel r = null;
					Map<Integer, Integer> found = new HashMap<Integer, Integer>();

					if (port != null) {
						refill:
							for (int slot: getFuelSlots()) {
								for (MachineFuel recipe: recipes) {
									if (SlimefunManager.isItemSimiliar(port.getItemInSlot(slot), recipe.getInput(), true)) {
										if (pushItems(l, new CustomItem(port.getItemInSlot(slot), 1), getFuelSlots()) == null) {
											port.replaceExistingItem(slot, InvUtils.decreaseItem(port.getItemInSlot(slot), 1));
											break refill;
										}
									}
								}
							}
					}

					outer:
					for (MachineFuel recipe: recipes) {
						for (int slot: getFuelSlots()) {
							if (SlimefunManager.isItemSimiliar(BlockStorage.getInventory(l).getItemInSlot(slot), recipe.getInput(), true)) {
								found.put(slot, recipe.getInput().getAmount());
								r = recipe;
								break outer;
							}
						}
					}

					if (r != null) {
						for (Map.Entry<Integer, Integer> entry: found.entrySet()) {
							BlockStorage.getInventory(l).replaceExistingItem(entry.getKey(), InvUtils.decreaseItem(BlockStorage.getInventory(l).getItemInSlot(entry.getKey()), entry.getValue()));
						}
						processing.put(l, r);
						progress.put(l, r.getTicks());
					}
					return 0;
				}
			}

			@Override
			public boolean explode(final Location l) {
				final boolean explosion = explode.contains(l);
				if (explosion) {
					BlockStorage.getInventory(l).close();
					
					Bukkit.getScheduler().scheduleSyncDelayedTask(SlimefunStartup.instance, new Runnable() {
						@Override
						public void run() {
							ReactorHologram.remove(l);
						}
					}, 0);
					
					explode.remove(l);
					processing.remove(l);
					progress.remove(l);
				}
				return explosion;
			}
		});

		super.register(slimefun);
	}

	private Inventory inject(Location l) {
		int size = BlockStorage.getInventory(l).toInventory().getSize();
		Inventory inv = Bukkit.createInventory(null, size);
		for (int i = 0; i < size; i++) {
			inv.setItem(i, new CustomItem(Material.COMMAND, " &4ALL YOUR PLACEHOLDERS ARE BELONG TO US", 0));
		}
		for (int slot: getOutputSlots()) {
			inv.setItem(slot, BlockStorage.getInventory(l).getItemInSlot(slot));
		}
		return inv;
	}

	private Inventory inject(Location l, int[] slots) {
		int size = BlockStorage.getInventory(l).toInventory().getSize();
		Inventory inv = Bukkit.createInventory(null, size);
		for (int i = 0; i < size; i++) {
			inv.setItem(i, new CustomItem(Material.COMMAND, " &4ALL YOUR PLACEHOLDERS ARE BELONG TO US", 0));
		}
		for (int slot: slots) {
			inv.setItem(slot, BlockStorage.getInventory(l).getItemInSlot(slot));
		}
		return inv;
	}

	public void pushItems(Location l, ItemStack item) {
		Inventory inv = inject(l);
		inv.addItem(item);

		for (int slot: getOutputSlots()) {
			BlockStorage.getInventory(l).replaceExistingItem(slot, inv.getItem(slot));
		}
	}

	public ItemStack pushItems(Location l, ItemStack item, int[] slots) {
		Inventory inv = inject(l, slots);
		Map<Integer, ItemStack> map = inv.addItem(item);

		for (int slot: slots) {
			BlockStorage.getInventory(l).replaceExistingItem(slot, inv.getItem(slot));
		}

		for (Map.Entry<Integer, ItemStack> entry: map.entrySet()) {
			return entry.getValue();
		}

		return null;
	}

	public abstract ItemStack getProgressBar();

	public Set<MachineFuel> getFuelTypes() {
		return this.recipes;
	}

	public BlockMenu getAccessPort(Location l) {
		Location portL = new Location(l.getWorld(), l.getX(), l.getY() + 3, l.getZ());
		if (BlockStorage.check(portL, "REACTOR_ACCESS_PORT")) return BlockStorage.getInventory(portL);
		return null;
	}
}
